package cn.cxyxj.study.webflux03.authorization;

import cn.cxyxj.study.webflux03.constant.Constant;
import cn.cxyxj.study.webflux03.entity.SysUserDetails;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Mono;

import java.util.HashSet;
import java.util.Set;

/**
 * @author cxyxj
 */
public class CustomReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private Logger logger = LoggerFactory.getLogger(CustomReactiveAuthorizationManager.class);

    /**
     * 登录就能访问的资源，不需要赋予角色
     */
    private static final Set<String> LOGIN_URL_INCLUDE_FILTERS = new HashSet<>();
    static {
        LOGIN_URL_INCLUDE_FILTERS.add("/hello");
    }
    /**
     *
     * @param authentication：身份验证对象
     * @param authorizationContext：请求信息
     * @return
     */
    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) {
        logger.info("CustomReactiveAuthorizationManager check");
        // 如果是匿名用户，则直接返回 false。
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        String token = request.getHeaders().getFirst(Constant.AUTHORIZATION);
        if (StringUtils.isBlank(token)) {
            return Mono.just(new AuthorizationDecision(false));
        }
        String uri = request.getPath().pathWithinApplication().value();
        PathMatcher matcher = new AntPathMatcher();
        String finalUri = uri;
        //先从资源中匹配，如果存在，直接放行
        boolean staticUrlFlag = LOGIN_URL_INCLUDE_FILTERS.stream().anyMatch(u -> matcher.match(u, finalUri));
        if (staticUrlFlag) {
            return Mono.just(new AuthorizationDecision(true));
        }
        return authentication.filter(Authentication::isAuthenticated)
                        .map(a -> a.getPrincipal())
                .flux().any(p -> {
                            if (p instanceof SysUserDetails) {
                                // 从redis中匹配当前请求是否有权限
                                Set<String> urlSet = ((SysUserDetails) p).getUrlSet();
                                return urlSet.stream().anyMatch(u -> matcher.match(u, finalUri));
                            }
                            return false;
                        }
                ).map(hasAuthority -> new AuthorizationDecision(hasAuthority));
    }
}
